from pyparsing import *

class MacroNotFound(Exception):
    def __init__(self, name):
        self.name = name
        
    def __repr__(self):
        return self.name
        
class MacroIncorrectParameters(Exception):
    def __init__(self, name, params):
        self.name = name
        self.params = params
        
    def __repr__(self):
        return "Incorrect parameters for '%s': %s" % (self.name, self.params)

class RangeType(object):
    def __init__(self, start, end, skip=None):
        self.start = start
        self.end = end
        self.skip = skip
        self.value = range(self.start, self.end, self.skip if self.skip else 1)
        
    def __getitem__(self, index):
        return self.value[index]
        
    def __iter__(self):
        return iter(self.value)
            
class ListType(object):
    def __init__(self, *args):
        super(ListType, self).__init__()
        self.value = []
        if len(args) == 1:
            if isinstance(args[0], int):
                self.value = [None for i in range(args[0])]
            elif isinstance(args[0], ListType):
                self.value = list(args[0].value)
            elif isinstance(args[0], list):
                self.value = args[0]
        #endif

    def __getitem__(self, index):
        return self.value[index]

    def __setitem__(self, index, value):
        self.value[index] = value

    def __iter__(self):
        return iter(self.value)
        
class DictType(object):
    def __init__(self, value):
        self.value = value
        
    def __getitem__(self, index):
        return self.value[index]
        
    def __setitem__(self, index, value):
        self.value[index] = value
        
    def __iter__(self):
        return iter(self.value.keys())
        
class UnprocessedBlock(object):
    def __init__(self, content):
        self.content = content
        
    def __iter__(self):
        return iter(self.content)
        
class LiteralBlock(object):
    def __init__(self, content):
        self.content = content
        
    def __repr__(self):
        return self.content
        
class NamedParameter(object):
    def __init__(self, name, value):
        self.name = name
        self.value = value
        
    def __repr__(self):
        return "<%s=%s>" % (self.name, self.value)
        
class Variable(object):
    def __init__(self, name, indexed):
        self.name = name[1:]
        self.value = None
        self.ctx = None        
        self.index = indexed if indexed != "" else None        
        
class ParameterList(object):
    def __init__(self, params=None):
        self.args = []
        self.kwargs = {}
        if params != None:   
            for param in params:
                if isinstance(param, NamedParameter):
                    self.kwargs[param.name] = param.value
                else:
                    self.args.append(param)
            #endfor
        #endif
    
    def __repr__(self):
        return "[args=%s, kwargs=%s]" % (self.args, self.kwargs)
        
class MacroCall(object):
    def __init__(self, name, parameters, block, delayed, spacer):
        self.name = name
        
        if isinstance(parameters, ParameterList):
            self.parameters = parameters
        else:
            self.parameters = ParameterList()
            
        self.block = block
        self.delayed = delayed
        self.spacer = True if spacer == ';' else False
        
    def __repr__(self):
        return "%s(%s) %s" % (self.name, self.parameters, self.block)
        
class AliasCall(object):
    def __init__(self, name):
        self.name = name

class Grammar(object):
    def __init__(self):        
        lbrace = Literal("{")
        rbrace = Literal("}")
        lbracel = Literal("{:")
        rbracel = Literal(":}")
        lbracket = Literal("[")
        rbracket = Literal("]")
        
        decorator = Literal("#") ^ Literal("@")
        decorator.setParseAction(lambda t: t[0] == "@")
        
        name =  Combine(Word(alphas) + Optional(Word(alphanums + "_")))
        integerValue = Regex(r'[+-]?\d+')
        integerValue.setParseAction(lambda t: int(t[0]))

        floatValue = Regex(r'\d+(\.\d*)?([eE]\d+)?')
        floatValue.setParseAction(lambda t: float(t[0]))

        boolValue = Literal("true") | Literal("false")
        boolValue.setParseAction(lambda t: True if t[0] == "true" else False )

        stringValue = name | QuotedString(quoteChar='"', unquoteResults=True)
        
        rangeValue = Word(nums).setResultsName("start") + ":" + Word(nums).setResultsName("end") + Optional(":" + Word(nums).setResultsName("skip"))
        rangeValue.setParseAction(lambda s, l, t: self.makeRange(t))

        index = Forward()
        variable = Combine("$" + Word(alphas) + Optional(Word(alphanums + "_"))).setResultsName("name") + Optional(Literal("[") + index.setResultsName("indexed") + Literal("]"))
        variable.setParseAction(lambda t: Variable(t.name, t.indexed))
        
        #indexedVariable = Combine("$" + Word(alphas).setResultsName("name") + Optional(Word(alphanums + "_"))) + Literal("[") + index.setResultsName("indexed") + Literal("]")
        
        index << (variable ^ integerValue ^ stringValue ^ name)#^ stringValue ^ name
        #index.setParseAction(lambda t: t)
        
        listValue = Forward()
        listValues = integerValue ^ floatValue ^ boolValue ^ stringValue ^ listValue
        listValues.setParseAction(lambda t: t[0])
        
        listValue << lbracket.suppress() + delimitedList(listValues, ",")("valueList") + rbracket.suppress()
        listValue.setParseAction(lambda t: ListType(list(t.valueList)))
        
        anonymousBlock = Forward()
        macroCall = Forward()        
        value = variable ^ integerValue ^ floatValue ^ boolValue ^ stringValue ^ listValue ^ rangeValue ^ macroCall ^ anonymousBlock

        literalBlock = Suppress(lbracel) + SkipTo(rbracel) + Suppress(rbracel)
        literalBlock.setParseAction(lambda s,l,t: LiteralBlock(t[0]))

        # want to re-implement at some point
        comment = Suppress("/*" + SkipTo("*/") + "*/").addParseAction(lambda t: None)
        
        escapedSymbols = Literal("\\$") | Literal("\\#")
        escapedSymbols.setParseAction(lambda t: t[1:])

        block = Forward()
        text = OneOrMore(CharsNotIn(r"/\${}#@") | comment | Literal("/"))
        #text = Regex(r"|[^\\${}#@])+").leaveWhitespace()
        blockContents = (comment | escapedSymbols | variable | text | macroCall | literalBlock | block)
        block << lbrace + ZeroOrMore(blockContents).setResultsName("content") + rbrace
        block.setParseAction(lambda t: UnprocessedBlock(t.content))

        blockExpr = literalBlock ^ block

        namedParameter = name.setResultsName("name") + "=" + value.setResultsName("value")
        namedParameter.setParseAction(lambda t: NamedParameter(t.name, t.value))
        
        parameter = namedParameter | value
        parameterList = Suppress(Literal("(")) + Group(Optional(parameter + ZeroOrMore(Suppress(",") + parameter))) + Suppress(Literal(")"))
        parameterList.setParseAction(lambda t: ParameterList(t[0]))
                        
        macroCall << decorator.setResultsName("delayed") + name.setResultsName("name") + Optional(parameterList.setResultsName("params")) + Optional(blockExpr.setResultsName("block")) + Optional(Literal(";").setResultsName("spacer"))
        macroCall.setParseAction(lambda t: MacroCall(t.name, t.params, t.block, t.delayed, t.spacer))
        
        anonymousBlock << decorator.setResultsName("delayed") + blockExpr.setResultsName("block") + Optional(Literal(";").setResultsName("spacer"))
        anonymousBlock.setParseAction(lambda t: MacroCall("anon", None, t.block, t.delayed, t.spacer))
                
        self.doc = macroCall | comment
        #self.doc.setDefaultWhitespaceChars("")
        
    @staticmethod
    def makeRange(t):
        if len(t.skip) == 0:
            return RangeType(int(t.start), int(t.end))
        else:
            return RangeType(int(t.start), int(t.end), int(t.skip))
            
    @staticmethod
    def display(msg, t):
        print "%s %s" % (msg, t)
                    
    def findMacros(self, content):
        return self.doc.scanString(content)
